package jeconbond.automata.behaviour;

import java.util.ArrayList;
import java.util.List;

import jeconbond.automata.INode;
import jeconbond.automata.IOutcome;
import jeconbond.automata.behaviour.ListPlanWalker;
import jeconbond.automata.behaviour.IWalkStrategy;
import jeconbond.economic.production.IResourceNecessityInformer;
import jeconbond.economic.resources.ResourceBasket;
import jeconbond.economic.resources.ResourceStorrage;
import jeconbond.economic.resources.IResourceType;

public class NaturalAuditWalkStrategy implements IWalkStrategy, IResourceNecessityInformer {
	private ListPlanWalker listPlanWalker;
	private INode root;
	private double[] goalArray;
	private int[] step_ptr;
	private IResourceType tankResource;
	private IResourceType traktorResource;

	public NaturalAuditWalkStrategy(
			ResourceStorrage inputRS, 
			ResourceStorrage outputRS,
			INode root,
			double[] goalArray,
			int[] step_ptr,
			IResourceType tankResource,
			IResourceType traktorResource
	) {
		this.root = root;
		this.goalArray = goalArray;
		this.step_ptr = step_ptr;
		this.tankResource = tankResource;
		this.traktorResource = traktorResource;
		listPlanWalker = new ListPlanWalker(
				new ArrayList<IOutcome>(),
				inputRS,
				outputRS
		);
		doAudit();
	}
	
	public class FindBestResultContext {
		private double bestFound = Double.POSITIVE_INFINITY;
		private double tanks = 0.0;
		private double traktors = 0.0;
		
		private List<IOutcome> plan;
		private double goal;
		
		public FindBestResultContext(
				List<IOutcome> plan, 
				double goal 
		) {
			this.plan = plan;
			this.goal = goal;
		}

		public void find() {
			plan.clear();
			dfs(root);
			revertOrder();
		}

		private boolean dfs(INode n) {
			boolean inBestTrunk = false;
			for (IOutcome out : n.getOutcomes()) {
				procOut(out, 1);
				{
					INode dest = out.getDestination();
					boolean outIsBest = false;
					if (dest == root) {
						outIsBest = checkIfBest();
					} else {
						outIsBest = dfs(dest);
					}
					if (outIsBest) {
						plan.add(out);
					}
					inBestTrunk |= outIsBest;
				}
				procOut(out, -1);
			}
			return inBestTrunk;
		}

		private boolean checkIfBest() {
			boolean result = false;
			double rel = tanks / traktors;
			double dist;
			if (Double.isInfinite(rel) && Double.isInfinite(goal)) {
				result = true;
				dist = 0.0;
			} else {
				dist = Math.abs(Math.log(rel) - Math.log(goal));
				if (dist < bestFound || Double.isInfinite(bestFound)) {
					result = true;
				}
			}
			if (result) {
				bestFound = dist;
				plan.clear();
			}
			return result;
		}

		private void procOut(IOutcome out, int mul) {
			for (ResourceBasket rb : out.getProducedResources()) {
				if (rb.type == tankResource) {
					tanks += rb.value * mul;
				}
				if (rb.type == traktorResource) {
					traktors += rb.value * mul;
				}
			}
		}
		
		private void revertOrder() {
			int n = plan.size();
			int k = n / 2;
			for (int i = 0; i < k; i++ ) {
				IOutcome t = plan.get(i);
				plan.set(i, plan.get(n - i - 1));
				plan.set(n - i - 1, t);
			}
		}
	}

	private void doAudit() {
		List<IOutcome> plan = listPlanWalker.getPlan();
		double goal = goalArray[step_ptr[0]];
		FindBestResultContext fbrc = new FindBestResultContext(plan, goal);
		fbrc.find();
		listPlanWalker.refreshPlan(0, 1);
	}

	@Override
	public IOutcome doWalk(INode state) {
		if (state == root) {
			doAudit();
		}
		return listPlanWalker.doWalk(state);
	}

	@Override
	public List<InfoItem> listInfoItems() {
		return listPlanWalker.listInfoItems();
	}
}
